namespace hjc {

	export class DraggerManager extends Singleton {

		protected constructor(
			private $target: egret.DisplayObject,
			private $lastX: number,
			private $lastY: number,
			private $param: MoveParam,
			private $draggerMap: Map<number, DragOptions>,
			private $effectiveMap: Map<number, egret.DisplayObject>,
		) {
			super();
			this.$param = { fromX: 0, fromY: 0, movedX: 0, movedY: 0, valid: true };
			this.$draggerMap = new Map();
			this.$effectiveMap = new Map();
		}
		public attach<T extends egret.DisplayObject>(obj: T, options?: DragOptions): T {
			if (this.$draggerMap.has(obj.hashCode)) return obj;
			let effective = (!!options && !!options.effective) ? options.effective : obj;
			effective.touchEnabled = true;
			effective.addEventListener(egret.TouchEvent.TOUCH_BEGIN, this._onBegin, this);
			this.$draggerMap.set(obj.hashCode, options);
			this.$effectiveMap.set(effective.hashCode, obj);
			return obj;
		}

		public dettach<T extends egret.DisplayObject>(obj: T): T {
			if (!this.$draggerMap.has(obj.hashCode)) return obj;
			let options = this.$draggerMap.get(obj.hashCode);
			let effective = !!options.effective ? options.effective : obj;
			effective.removeEventListener(egret.TouchEvent.TOUCH_BEGIN, this._onBegin, this);
			this.$draggerMap.delete(obj.hashCode);
			this.$effectiveMap.delete(effective.hashCode);
			return obj;
		}

		private _onBegin(e: egret.TouchEvent): void {
			this.$target = this.$effectiveMap.get(e.currentTarget.hashCode) as egret.DisplayObject;
			this.$lastX = e.stageX;
			this.$lastY = e.stageY;
			this.$param.fromX = this.$target.x;
			this.$param.fromY = this.$target.y;
			this.$param.movedX = 0;
			this.$param.movedY = 0;
			this.$param.valid = true;
			game.stage.addEventListener(egret.TouchEvent.TOUCH_MOVE, this._onMove, this);
			game.stage.addEventListener(egret.TouchEvent.TOUCH_END, this._onEnd, this);
			if (isFunc(this.$target['onDragBegin'])) this.$target['onDragBegin'](this.$param);
		}

		private _onMove(e: egret.TouchEvent): void {
			let moveX = (e.stageX - this.$lastX) / game.scale;
			let moveY = (e.stageY - this.$lastY) / game.scale;
			this._checkAttempt(moveX, moveY);
			this.$lastX = e.stageX;
			this.$lastY = e.stageY;
			if (isFunc(this.$target['onDragMove'])) this.$target['onDragMove'](this.$param);
		}

		private _onEnd(): void {
			game.stage.removeEventListener(egret.TouchEvent.TOUCH_MOVE, this._onMove, this);
			game.stage.removeEventListener(egret.TouchEvent.TOUCH_END, this._onEnd, this);
			if (isFunc(this.$target['onDragEnd'])) this.$target['onDragEnd'](this.$param);
		}

		private _checkAttempt(moveX: number, moveY: number): void {
			let options = this.$draggerMap.get(this.$target.hashCode);
			this.$param.valid = true;
			if (!!options && !!options.scope) {
				let [x, y] = [this.$target.x, this.$target.y];
				this.$target.x = this._limit(x, moveX, options.scope.x - this.$param.fromX, options.scope.x + options.scope.width - this.$param.fromX);
				this.$target.y = this._limit(y, moveY, options.scope.y - this.$param.fromY, options.scope.y + options.scope.height - this.$param.fromY);
				this.$param.movedX = this.$target.x - x;
				this.$param.movedY = this.$target.y - y;
			} else if (!!options && !!options.box) {
				hjc.todo('drag options.box');
			} else if (!!options && !!options.yoke) {
				hjc.todo('drag options.yoke');
			} else {
				this.$param.movedX = moveX;
				this.$param.movedY = moveY;
				this.$target.x += this.$param.movedX;
				this.$target.y += this.$param.movedY;
			}
		}

		private _limit(cur: number, changed: number, floor: number, ceil: number): number {
			if (cur + changed < floor) { this.$param.valid = false; return floor; }
			if (cur + changed > ceil) { this.$param.valid = false; return ceil; }
			else return cur + changed;
		}
	}
}